#if defined __GNUC__
# define _POSIX_C_SOURCE 200809L
#else
# define _XOPEN_SOURCE 700
#endif

#include <fcntl.h>
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>

#include "t4.h"

#define FIFO_MODE S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
#define SEC_TO_NANO 1e9 /* billion */

int
t4_term_set(void)
{
	struct termios settings;

	if (tcgetattr(STDIN_FILENO, &settings) < 0)
		return 0;

	/* turns off canonical mode so we don't go line by line */
	settings.c_lflag &= ~ICANON;

	/* blocks read until we have atleast one byte */
        settings.c_cc[VMIN] = 0;
	settings.c_cc[VTIME] = 0;

	if (tcsetattr(STDIN_FILENO, TCSANOW, &settings) < 0)
		return 0;

	return 1;
}

void
t4_in_init(T4_in *in, float secs)
{
	in->secs = secs;
	time(&in->time);
}

int
t4_in_status(void)
{
	int size;

	if (ioctl(STDIN_FILENO, FIONREAD, &size) < 0)
		return -1;

	return size;
}

int
t4_in_empty(void)
{
        char buf[64];
	int amt;

	while ((amt = read(STDIN_FILENO, &buf, sizeof(buf))) != 0)
		if (amt < 0)
			return 0;

	return 1;
}

enum t4_in_val
t4_in(T4_in *in, T4_dat *dat)
{
	time_t now;
        unsigned int size;

	time(&now);

	if (difftime(now, in->time) < in->secs)
		return T4_IN_WAIT;

	if (ioctl(STDIN_FILENO, FIONREAD, &size) < 0)
		return T4_IN_ERR;

	if (!size)
		return T4_IN_NONE;

	if (dat->max < size) {
		free(dat->str);

		if (!(dat->str = malloc(size)))
			return T4_IN_ERR;

		dat->max = size;
	}

	if (read(STDIN_FILENO, dat->str, size) < size)
		return T4_IN_ERR;

	dat->len = size;
	return T4_IN_STUFF;
}

int
t4_out_init(T4_out *out, const char *path, double secs, size_t chunk)
{
	out->chunk = chunk;
	out->secs = secs;

	if (access(path, W_OK) < 0 &&
	    mkfifo(path, FIFO_MODE) < 0)
		return 0;

	if ((out->pipe = open(path, O_WRONLY)) < 0)
		return 0;

	return 1;
}

int
t4_out_dispose(T4_out *out)
{
	return close(out->pipe);
}


static size_t
next_utf8(const char *str)
{
	char c = *str;
	size_t len = 0;

	for (; c >> 7; ++len, c = c << 1);

	if (!len)
		return 1;

	return len;
}

static size_t
utf8_size(const char *str, int units, size_t rest)
{
	size_t len = 0;
	size_t unit_size;

	for (; units && rest; --units, rest -= unit_size, len += unit_size)
		unit_size = next_utf8(str + len);

	return len;
}

int
t4_out(T4_out *out, T4_dat *dat, T4_brk *brk)
{
	size_t chunk_size;
	const char *pos = dat->str;
	size_t size = dat->len;
	struct timespec timer = {
		.tv_sec = trunc(out->secs),
		.tv_nsec = SEC_TO_NANO * (out->secs - trunc(out->secs))
	};

	if (brk && !brk->done) {
		pos += brk->amt;
		size -= brk->amt;
	}

	for (; size; pos += chunk_size, size -= chunk_size) {
		if (brk) {
			int input;

			if (ioctl(STDIN_FILENO, FIONREAD, &input) < 0)
				return 0;

			if (input) {
				brk->done = 0;
				brk->amt = pos - dat->str;
				return 1;
			}
		}

		chunk_size = utf8_size(pos, out->chunk, size);

		if (write(out->pipe, pos, chunk_size) < (ssize_t) chunk_size)
			return 0;

		if (nanosleep(&timer, NULL) < 0)
			return 0;
	}

	if (brk)
	        brk->done = 1;

	return 1;
}

int
t4_dat_init(T4_dat *dat, const char *str)
{
	if (!str) {
		dat->max = dat->len = 0;
		dat->str = NULL;
		return 1;
	}

        dat->max = dat->len = strlen(str);

	if (!(dat->str = malloc(dat->max)))
		return 0;

	memcpy(dat->str, str, dat->max);
	return 1;
}

void
t4_dat_dispose(T4_dat *dat)
{
	free(dat->str);
}

int
t4_dat_set(T4_dat *dat, const char *str)
{
	dat->len = strlen(str);

	if (dat->max < dat->len) {
		dat->max = dat->len;
		free(dat->str);

		if (!(dat->str = malloc(dat->max)))
			return 0;
	}

	memcpy(dat->str, str, dat->len);
	return 1;
}
